package com.agrm.gateway.filter;

import com.agrm.model.common.dtos.ResponseResult;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

@Component
public class AuthFilter implements GlobalFilter {
    private static final Logger log = Logger.getLogger(AuthFilter.class.getName());
    @Value("${jwt.secret.key}")
    private String jwtSecret;
    private ObjectMapper objectMapper;
    private GatewayConfig gatewayConfig;
    private WebClient.Builder webClientBuilder;
    @Value("${rbac.gateway}")
    private String gateway;

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Autowired
    public void setGatewayConfig(GatewayConfig gatewayConfig) {
        this.gatewayConfig = gatewayConfig;
    }

    @Autowired
    public void setWebClientBuilder(WebClient.Builder webClientBuilder) {
        this.webClientBuilder = webClientBuilder;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest req = exchange.getRequest();
        String uri = req.getPath().toString();//当前请求路径
        String ctx = req.getPath().contextPath().value();//上下文路径
        uri = uri.replace(ctx, "");//无上下文的请求路径
        String method = req.getMethod().toString();

        final String finalUri = uri;

        //对白名单中的地址直接放行
        List<String> ignoreUrls = gatewayConfig.getWhiteList();
        PathMatcher matcher = new AntPathMatcher();
        for (String pattern : ignoreUrls) {
            UriPattern up = UriPattern.of(pattern);
            if (matcher.match(up.pattern(), uri) && up.matchMethod(method)) {
                return chain.filter(exchange);//直接放行
            }
        }

        String jwt = req.getHeaders().getFirst("Authorization");
        if (!StringUtils.hasText(jwt)) {
            jwt = req.getQueryParams().getFirst("jwt");
        }

        if (StringUtils.hasText(jwt)) {
            //校验token
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecret)).build();

            //从jwt中取出有效数据，进行业务使用，如从Redis中获取用户数据
            try {
                DecodedJWT dj = jwtVerifier.verify(jwt);

                Map<String, Claim> claims = dj.getClaims();

                //从session或Redis中取出用户数据
                Integer userId = claims.get("id").asInt();//用户id
                String username = dj.getAudience().get(0);//用户名

                //鉴权
                System.out.println(userId + ":" + username);
                //1.找出当前请求所需要的权限，resource_perm_mappings
                WebClient webClient = webClientBuilder.build();
                return webClient.get().uri("http://" + gateway + "/api/v1/rbac/resource_perm_mappings")
                        .retrieve().bodyToMono(ResponseResult.class)
                        .flatMap(it -> {
                            Map<String, List<String>> resourcePermMappings = (Map<String, List<String>>) it.getData();
                            //2.找出当前用户所有的权限
                            return webClient.get().uri("http://" + gateway + "/api/v1/rbac/perms/{userId}/true", userId)
                                    .retrieve().bodyToMono(ResponseResult.class)
                                    .flatMap(it1 -> {
                                        List<String> perms = (List<String>) it1.getData();//当前用户拥有的所有权限
                                        if (hasPerm(finalUri, method, perms, resourcePermMappings)) {
                                            //return chain.filter(exchange);//放行
                                            String authInfo = userId + ":" + username;
                                            authInfo = Base64.getEncoder().encodeToString(authInfo.getBytes());
                                            ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
                                                    .header("x-auth-info", authInfo)
                                                    .build();
                                            return chain.filter(exchange.mutate().request(mutatedRequest).build());
                                        } else {//鉴权未通过
                                            return writeJson(exchange.getResponse(), HttpStatus.FORBIDDEN, ResponseResult.errorResult(-1,"无权访问"));
                                        }
                                    });
                        });
            } catch (JWTVerificationException e) {
                log.log(Level.SEVERE, "jwt校验异常", e);
                return writeJson(exchange.getResponse(), HttpStatus.UNAUTHORIZED,ResponseResult.errorResult(-1,"jwt无效或已过期"));
            }
        } else {
            return writeJson(exchange.getResponse(), HttpStatus.UNAUTHORIZED, ResponseResult.errorResult(-1,"无jwt，请重新认证"));
        }
    }

    //渲染json数据到前端
    private Mono<Void> writeJson(ServerHttpResponse resp, HttpStatus status, ResponseResult jsonResult) {
        resp.setStatusCode(status);//401状态码
        resp.getHeaders().add("Content-Type", "application/json;charset=utf-8");

        try {
            String json = objectMapper.writeValueAsString(jsonResult);
            DataBuffer db = resp.bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8));
            return resp.writeWith(Mono.just(db));
        } catch (JsonProcessingException ex) {
            throw new RuntimeException("json序列化异常", ex);
        }
    }

    private record UriPattern(String[] allowedMethods, String pattern) {
        //判断是否匹配某个请求类型
        private boolean matchMethod(String method) {
            for (String am : allowedMethods) {
                if (am.equalsIgnoreCase(method) || "*".equals(am)) {
                    return true;
                }
            }
            return false;
        }

        private static UriPattern of(String uri) {
            String[] parts = uri.split(":");
            if (parts.length == 1) {
                return new UriPattern(new String[]{"*"}, parts[0]);
            } else {
                return new UriPattern(parts[0].split(","), parts[1]);
            }
        }
    }

    //判断用户对uri是否拥有权限
    private boolean hasPerm(String uri, String method, List<String> userPerms, Map<String, List<String>> resourcePermMappings) {
        PathMatcher matcher = new AntPathMatcher();
        //1.找到访问uri所需要的权限
        for (Map.Entry<String, List<String>> entry : resourcePermMappings.entrySet()) {
            String resource = entry.getKey();//资源，模式
            List<String> perms = entry.getValue();

            UriPattern up = UriPattern.of(resource);

            if (matcher.match(up.pattern(), uri) && up.matchMethod(method)) {
                for (String perm : perms) {
                    if (userPerms.contains(perm)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

}
